package com.blue.base.demo.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.blue.base.common.exception.BaseException;
import com.blue.base.common.utils.json.JsonUtil;
import com.blue.base.common.utils.wechat.WeChatUtil;
import com.blue.base.demo.config.WechatConfig;
import com.blue.base.demo.dto.WxUserDecodeBindDto;
import com.blue.base.demo.entity.User;
import com.blue.base.demo.service.UserService;
import com.blue.base.demo.service.WxAuthService;
import com.blue.base.demo.vo.WeChatUserInfoVo;
import com.blue.base.demo.vo.WxSessionVo;
import com.blue.base.redis.utils.RedisUtils;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * 授权接口
 *
 * @author liulei
 * @version 2.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class WxAuthServiceImpl implements WxAuthService {

    /**
     * 微信会话缓存key
     */
    public static String WX_SESSION_PREFIX = "com:sys:user:auth:wechat:";

    private final UserService userService;
    private final WechatConfig wechatConfig;
    private final RestTemplate restTemplate;

    @Override
    public Map<String, Object> code2Session(String code) {
        Map<String, Object> result = Maps.newHashMapWithExpectedSize(2);

        // 1. 根据code调用小程序获取相关信息
        WxSessionVo wxSessionVo = this.invokeWeChatCode2Session(code);

        // 2. 缓存会话[key: 会话sessionId,value: 小程序返回授权信息,time: 5分钟]
        RedisUtils.set(WX_SESSION_PREFIX + wxSessionVo.getSessionId(), JSON.toJSONString(wxSessionVo), 300);

        // 3. 判断用户是否存在绑定记录/手机号
        User user = userService.validateUserExist(wxSessionVo.getOpenid(), wxSessionVo.getUnionid());
        boolean userNull = Objects.isNull(user);
        if (userNull || StringUtils.isEmpty(user.getPhone())) {
            if (userNull) {
                // 新增一条记录
                User insertBean = new User();
                insertBean.setUnionid(wxSessionVo.getUnionid()).setOpenid(wxSessionVo.getOpenid());
                this.userService.saveNewWeChatUser(insertBean);
            }
            // 返回不带手机号的用户信息
            result.put("authorization", weChatInfo2ReturnBean(wxSessionVo));
            result.put("sysUserName", "");
            return result;
        }
        // 4.构建带手机号的用户信息
        WeChatUserInfoVo userInfo = weChatInfo2ReturnBean(wxSessionVo);
        userInfo.setPhone(user.getPhone());
        result.put("authorization", userInfo);
        result.put("sysUserName", userInfo.getPhone());
        return result;
    }

    @Override
    public void bindUser(WxUserDecodeBindDto param) {
        // 1.判断会话是否存在
        String sessionStr = RedisUtils.get(WX_SESSION_PREFIX + param.getSessionId());
        if (StringUtils.isEmpty(sessionStr)) {
            throw new BaseException(30000, "会话失效，请重新进行授权");
        }
        WxSessionVo wxSession = JsonUtil.toBean(sessionStr, WxSessionVo.class);
        // 2.解析加密数据获取到手机号【待完成】
        String mobilePhone = this.decodeWeChatInfo(wxSession, param);
        // 3.判断解析出来的电话是否存在绑定关系
        User user = userService.validateUserExist(wxSession.getOpenid(), wxSession.getUnionid());
        if (Objects.isNull(user)) {
            user.setOpenid(wxSession.getOpenid())
                    .setUnionid(wxSession.getUnionid())
                    .setPhone(mobilePhone);
            userService.saveNewWeChatUser(user);
        } else {
            // 维护用户电话信息
            userService.updateMobileByWxInfo(wxSession.getOpenid(), wxSession.getUnionid(), mobilePhone);
        }
    }

    @Override
    public WxSessionVo invokeWeChatCode2Session(String code) {
        log.info("wx|auth|code2Session|{}", code);
        StringBuilder code2SessionUrl = new StringBuilder(wechatConfig.getCode2SessionUrl());
        code2SessionUrl.append("?appid=").append(wechatConfig.getAppid())
                .append("&secret=").append(wechatConfig.getSecret())
                .append("&js_code=").append(code)
                .append("&grant_type=authorization_code");
        String originResult = restTemplate.getForObject(code2SessionUrl.toString(), String.class);
        // unionId暂时无法返回
        WxSessionVo wxSessionVo = JsonUtil.toBean(originResult, WxSessionVo.class);
        if (!StringUtils.isEmpty(wxSessionVo.getErrcode())) {
            log.error("wx|auth|code2session|error|{}", originResult);
            throw new BaseException(30000, "微信授权异常，请检查配置是否正常");
        }
        wxSessionVo.setSessionId(UUID.randomUUID().toString().replace("-", ""));
        log.info("wx|auth|code2Session|result|{}", wxSessionVo);
        return wxSessionVo;
    }

    @Override
    public String decodeWeChatInfo(WxSessionVo session, WxUserDecodeBindDto param) {
        if (StringUtils.isEmpty(session.getSession_key())) {
            log.error("auth|wechat|decode|user|info|miss|session|key|{}", session);
            throw new BaseException(30000, "微信解析用户信息失败");
        }
        JSONObject json;
        try {
            json = WeChatUtil.decryptData(session.getSession_key(), param.getIv(), param.getEncryptedData());
            log.info("auth|wechat|decode|user|ingo|result|{}", json);
        } catch (Exception e) {
            log.error("auth|wechat|decode|user|info|error", e);
            throw new BaseException(30000, "微信解析用户信息失败");
        }
        Object phoneNumber = json.get("phoneNumber");
        if (phoneNumber == null) {
            log.error("auth|wechat|decode|user|info|have|no|phone");
            throw new BaseException(30000, "微信解析用户信息失败");
        }
        return phoneNumber.toString();
    }


    /**
     * 微信授权信息bean转换
     *
     * @param wxSessionVo 微信授权信息
     * @return 自定义bean
     */
    private WeChatUserInfoVo weChatInfo2ReturnBean(WxSessionVo wxSessionVo) {
        WeChatUserInfoVo wxUser = new WeChatUserInfoVo();
        wxUser.setOpenId(wxSessionVo.getOpenid());
        wxUser.setUnionId(wxSessionVo.getUnionid());
        // 使用自定义sessionId
        wxUser.setSessionId(wxSessionVo.getSessionId());
        return wxUser;
    }
}
